//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.UIDesigner
{
    #region NameSpace
    using Microsoft.Research.ScientificWorkflow.TridentModel;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;
    using System.Collections.Generic;
    using System.Linq;
    using System.Resources;
    using System.Text;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media.Imaging;
    using System.Windows.Media;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    using System.Windows;
    using System;
    #endregion

    /// <summary>
    /// Interaction logic for LogicalOperatorActivityBody
    /// </summary>
    public class LogicalOperatorActivityBody : TridentUIActivityElement
    {
        #region private members

        private SimpleActivityModel activityModel;
        private int inputParamRowCounter;

        private int outputParamRowCounter;

        private Border operatorBorder;

        private TextBlock txtOperatorName;

        private const double FIRSTCOLUMNWIDTH = 6.9;
        private const double SECONDCOLUMNWIDTH = 38.1;
        private const double THIRDCOLUMNWIDTH = 3;

        private Border inputParamBorder;

        #endregion

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="model">SimpleActivityModel.</param>
        /// <param name="dataTypes">In-used data type.</param>
        public LogicalOperatorActivityBody(SimpleActivityModel model, Dictionary<string, DataTypeModel> dataTypes)
            : base(model, dataTypes)
        {
            this.activityModel = model as SimpleActivityModel;
            this.InitBody();
            this.Name = model.UniqueId + "Element";
            this.txtOperatorName.Text = model.DisplayLabel;
            this.DataContext = this.activityModel;
            this.SetBorderBackground();
            this.AddInputParameters();
            this.AddOutputParameters();
            this.SetRowSpans();
            this.Loaded += new RoutedEventHandler(LogicalOperatorActivityBody_Loaded);
        }

        #region Properties
        public double Left
        {
            get
            {
                return Canvas.GetLeft(this);
            }
            set
            {
                Canvas.SetLeft(this, value);
            }
        }

        public double Top
        {
            get
            {
                return Canvas.GetTop(this);
            }
            set
            {
                Canvas.SetTop(this, value);
            }
        }
        #endregion

        #region public Methods

        /// <summary>
        /// This function is used to create the context menu 
        /// which will be used to delete the activities.
        /// </summary> 
        /// <param name="root">
        /// Specifies if the context menu is being generated for a root element.
        /// </param>
        public override void CreateContextMenu()
        {
            // Popup the Context menu only for Composer Tab
            if (this.ComposerCtrl != null && this.ComposerCtrl.CurrentRenderMode == RenderMode.ComposerMode)
            {
                this.ContextMenu = new ContextMenu();

                MenuItem deleteMenu = new MenuItem();
                deleteMenu.Header = TridentResourceManager.GetString("DeleteActivityLabel");

                deleteMenu.Icon = GetIcon(@"pack://Application:,,,/Microsoft.Research.ScientificWorkflow.TridentUtilities;component/Images/ScientificWorkflow/ActivitesDelete.png");
                deleteMenu.Click += new RoutedEventHandler(this.OnDeleteClick);
                this.ContextMenu.Items.Add(deleteMenu);

                MenuItem propertyPaneMenu = new MenuItem();
                propertyPaneMenu.Header = TridentResourceManager.GetString("PropertiesContextMenuLabel");

                propertyPaneMenu.Icon = GetIcon(@"pack://Application:,,,/Microsoft.Research.ScientificWorkflow.TridentUtilities;component/Images/ScientificWorkflow/ActivitiesProperties.png");
                propertyPaneMenu.Click += new RoutedEventHandler(this.OnPropClicked);
                this.ContextMenu.Items.Add(propertyPaneMenu);

                foreach (MenuItem item in this.ContextMenu.Items)
                {
                    item.IsEnabled = true;
                }
            }
            else if (this.ComposerCtrl != null && this.ComposerCtrl.CurrentRenderMode == RenderMode.ProvenanceMode)
            {
                this.CreateProvenanceMenu();
            }
        }

        /// <summary>
        /// Display the parameter outline.
        /// </summary>
        public override void ShowParameterOutline(bool showInputOutline)
        {
            if (showInputOutline)
            {
                BrushConverter converter = new BrushConverter();
                Brush color = converter.ConvertFrom(UIDesignerCodeConstants.PARAMETERBOXCOLOR) as Brush;
                this.inputParamBorder.BorderBrush = color;
            }
        }

        /// <summary>
        /// Hide the parameter outline.
        /// </summary>
        public override void HideParameterOutline(bool hideInputOutline)
        {
            if (hideInputOutline)
            {
                this.inputParamBorder.BorderBrush = Brushes.Transparent;
            }
        }

        /// <summary>
        /// Sets tthe background as Transparent
        /// </summary>
        public void SetTransparentBackground()
        {
            this.operatorBorder.Background = Brushes.Transparent;
            this.contentHolder.ColumnDefinitions[0].Width = new GridLength(13);
        }

        /// <summary>
        /// Apply seleted style to the workflow activity element.
        /// </summary>
        public override void ApplySelectedStyle()
        {
            // Apply the background of the border.
            this.MainBorder.BorderBrush = Brushes.Orange;
        }

        /// <summary>
        /// Apply Unseleted stlye to the workflow activity element.
        /// </summary>
        public override void ApplyUnselectedStyle()
        {
            // Revert back all the changes made while selecting.
            this.MainBorder.BorderBrush = new SolidColorBrush(Colors.Transparent);
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// The event handler for the loaded event.
        /// </summary>
        /// <param name="sender">Source</param>
        /// <param name="e">event args.</param>
        private void LogicalOperatorActivityBody_Loaded(object sender, RoutedEventArgs e)
        {
            this.CreateContextMenu();
        }

        /// <summary>
        /// Intialises the Logical operator Body
        /// </summary>
        private void InitBody()
        {
            this.Margin = new Thickness(0);
            this.Height = 40;
            this.Width = 50;
            this.Focusable = true;

            this.contentHolder.ColumnDefinitions.Add(LogicalOperatorActivityBody.CreateColumn(FIRSTCOLUMNWIDTH));
            this.contentHolder.ColumnDefinitions.Add(LogicalOperatorActivityBody.CreateColumn(SECONDCOLUMNWIDTH));
            this.contentHolder.ColumnDefinitions.Add(LogicalOperatorActivityBody.CreateColumn(THIRDCOLUMNWIDTH));

            this.operatorBorder = new Border();
            operatorBorder.Height = this.Height;
            operatorBorder.CornerRadius = new CornerRadius(5);
            operatorBorder.Name = "operatorBorder";
            operatorBorder.HorizontalAlignment = HorizontalAlignment.Stretch;

            Grid.SetColumnSpan(operatorBorder, 2);
            Grid.SetColumn(operatorBorder, 0);

            this.contentHolder.Children.Add(operatorBorder);

            this.txtOperatorName = new TextBlock();
            this.txtOperatorName.Name = "txtOperatorName";
            this.txtOperatorName.TextWrapping = TextWrapping.NoWrap;
            this.txtOperatorName.HorizontalAlignment = HorizontalAlignment.Center;
            this.txtOperatorName.Margin = new Thickness(-5, 0, 0, 0);
            this.txtOperatorName.Foreground = Brushes.Black;
            this.txtOperatorName.VerticalAlignment = VerticalAlignment.Center;
            Grid.SetColumn(this.txtOperatorName, 1);
            this.contentHolder.Children.Add(this.txtOperatorName);
            this.MainBorder = new Border();

            Grid.SetColumnSpan(this.MainBorder, 2);
            Grid.SetColumn(this.MainBorder, 0);
            this.MainBorder.BorderThickness = new Thickness(1.5);
            this.MainBorder.CornerRadius = new CornerRadius(5);
            this.Height = 40;
            this.Width = 50;
            this.contentHolder.Children.Add(this.MainBorder);

            this.inputParamBorder = new Border();
            Grid.SetColumn(this.inputParamBorder, 0);
            this.inputParamBorder.BorderThickness = new Thickness(1);
            this.inputParamBorder.CornerRadius = new CornerRadius(4);
            this.inputParamBorder.Width = 10;
            this.inputParamBorder.Margin = new Thickness(-7, 3, 0, 3);
            this.inputParamBorder.BorderBrush = Brushes.Transparent;
            Panel.SetZIndex(this.inputParamBorder, -1);
            this.contentHolder.Children.Add(this.inputParamBorder);
        }

        /// <summary>
        /// Creats a column definition with the specified width
        /// </summary>
        /// <param name="width">double Width</param>
        /// <returns>ColumnDefinition</returns>
        private static ColumnDefinition CreateColumn(double width)
        {
            ColumnDefinition column = new ColumnDefinition();
            column.Width = new GridLength(width);
            return column;
        }

        /// <summary>
        /// Set the operator Border Background color
        /// </summary>
        private void SetBorderBackground()
        {
            GradientStop firstGradientStop;
            GradientStop secondGradientStop;
            GradientStopCollection gradientCollection = new GradientStopCollection(2);
            if (this.txtOperatorName.Text.Equals("AND") || this.txtOperatorName.Text.Equals("NOT"))
            {
                firstGradientStop = new GradientStop((Color)ColorConverter.ConvertFromString("#FFEDFFEF"), 0);
                secondGradientStop = new GradientStop((Color)ColorConverter.ConvertFromString("#FF8ADA96"), 1);
            }
            else
            {
                firstGradientStop = new GradientStop((Color)ColorConverter.ConvertFromString("#FFFCFFED"), 0);
                secondGradientStop = new GradientStop((Color)ColorConverter.ConvertFromString("#FFCEDA8A"), 1);
                // Set the font weight bold foreground  for <, > operators
                this.txtOperatorName.Foreground = Brushes.Brown;
                this.txtOperatorName.FontSize = 16;
                this.txtOperatorName.FontWeight = FontWeights.Bold;
            }

            gradientCollection.Add(firstGradientStop);
            gradientCollection.Add(secondGradientStop);
            LinearGradientBrush lnrGradBrush = new LinearGradientBrush(gradientCollection, new Point(0.5, 0), new Point(0.5, 1));
            this.operatorBorder.BorderBrush = lnrGradBrush;
            this.operatorBorder.Background = lnrGradBrush;
        }

        /// <summary>
        /// Set the row span for the elements in the body.
        /// </summary>
        private void SetRowSpans()
        {
            int maxRows = this.inputParamRowCounter >= this.outputParamRowCounter ? this.inputParamRowCounter : this.outputParamRowCounter;
            Grid.SetRowSpan(operatorBorder, maxRows);
            Grid.SetRowSpan(this.MainBorder, maxRows);
            Grid.SetRowSpan(this.inputParamBorder, maxRows);
            Grid.SetRowSpan(this.txtOperatorName, maxRows);
        }

        /// <summary>
        /// Iterates ParameterDescriptionModel and add the Input parameter to Input stack
        /// </summary>
        private void AddInputParameters()
        {
            for (int counter = 0; counter < this.activityModel.InputParameters.Count; counter++)
            {
                ParameterDescriptionModel param = this.activityModel.InputParameters[counter] as ParameterDescriptionModel;
                if (param != null)
                {
                    // Skip the Optional Parameter
                    if (!param.IsMandatory)
                        continue;
                    ConditionParameter inputparameter = new ConditionParameter(param, ParameterFlowType.InputParameter, this.activityModel, this);
                    inputparameter.Name = this.activityModel.UniqueId + "_" + param.PropertyName;
                    Dictionary<string, object> dataHolder = new Dictionary<string, object>(2);
                    dataHolder.Add("ParameterDescriptionModel", param);
                    dataHolder.Add("ActivityModel", this.activityModel);
                    inputparameter.DataContext = dataHolder;

                    this.AddParameter(inputparameter);
                    inputParamRowCounter = inputParamRowCounter + 1;
                    this.InputParameterElements[param.PropertyName] = inputparameter;
                }
            }

            // Adding the Optional parameter 
            for (int counter = 0; counter < this.activityModel.InputParameters.Count; counter++)
            {
                ParameterDescriptionModel param = this.activityModel.InputParameters[counter] as ParameterDescriptionModel;
                if (param != null)
                {
                    // Skip the required parameter
                    if (param.IsMandatory)
                        continue;

                    ConditionParameter inputparameter = new ConditionParameter(param, ParameterFlowType.InputParameter, this.activityModel, this);
                    Dictionary<string, object> dataHolder = new Dictionary<string, object>(2);
                    dataHolder.Add("ParameterDescriptionModel", param);
                    dataHolder.Add("ActivityModel", this.activityModel);
                    inputparameter.DataContext = dataHolder;

                    this.AddParameter(inputparameter);
                    inputParamRowCounter = inputParamRowCounter + 1;
                    this.InputParameterElements[param.PropertyName] = inputparameter;
                }
            }
        }

        /// <summary>
        /// Adds the parameter to its appropriate column
        /// </summary>
        /// <param name="param">ConditionParameter</param>
        private void AddParameter(ConditionParameter param)
        {
            int colNumber = 0;
            int rowNumber = 0;
            if (param.ParamType == ParameterFlowType.InputParameter)
            {
                colNumber = 0;
                rowNumber = this.inputParamRowCounter;
                param.Margin = new Thickness(-5, 0, 0, 0);
            }
            else
            {
                colNumber = 2;
                rowNumber = this.outputParamRowCounter;
            }

            Grid.SetColumn(param, colNumber);
            if (this.contentHolder.RowDefinitions.Count == 0 || this.contentHolder.RowDefinitions.Count - 1 < rowNumber)
            {
                RowDefinition newRow = new RowDefinition();
                this.contentHolder.RowDefinitions.Add(newRow);
            }
            if (colNumber == 2)
            {
                // set RowSpan  for Output parameter
                Grid.SetRowSpan(param, this.inputParamRowCounter);
                param.HorizontalAlignment = HorizontalAlignment.Left;
                param.Margin = new Thickness(-5, 0, 0, 0);
            }

            Grid.SetRow(param, rowNumber);
            Panel.SetZIndex(param, 2);
            this.contentHolder.Children.Add(param);
        }

        /// <summary>
        /// Iterates ParameterDescriptionModel and add the Output parameter to Output stack
        /// </summary>
        private void AddOutputParameters()
        {
            for (int counter = 0; counter < this.activityModel.OutputParameters.Count; counter++)
            {
                ParameterDescriptionModel param = this.activityModel.OutputParameters[counter] as ParameterDescriptionModel;
                if (param != null)
                {
                    ConditionParameter outputparameter = new ConditionParameter(param, ParameterFlowType.OutputParameter, this.activityModel, this);
                    outputparameter.Name = this.activityModel.UniqueId + "_" + param.PropertyName;
                    outputparameter.VerticalAlignment = VerticalAlignment.Center;
                    outputparameter.Margin = new Thickness(0, 0, 0, 0);
                    this.AddParameter(outputparameter);
                    outputParamRowCounter = outputParamRowCounter + 1;
                    this.OutputParameterElements[param.PropertyName] = outputparameter;
                }
            }
        }

        private void OnDeleteClick(object sender, RoutedEventArgs e)
        {
            base.OnDelete();
        }

        private void OnPropClicked(object sender, RoutedEventArgs e)
        {
            base.ShowActivityProperties();
        }

        /// <summary>
        /// Fired on click of show properties.
        /// </summary>
        /// <param name="sender">
        /// Context menu.
        /// </param>
        /// <param name="e">
        /// RoutedEventArgs.
        /// </param>
        private void OnPropertiesMenuClicked(object sender, RoutedEventArgs e)
        {
            base.ShowActivityProperties();
        }

        #endregion

        #region IBindingEndpoint Members

        public override Point GetBindingStartPoint(UIElement relativeTo)
        {
            return new Point();
        }

        public override Point GetBindingEndpoint(UIElement relativeTo)
        {
            Point borderMidPoint = new Point(0, this.inputParamBorder.ActualHeight / 2);
            return this.inputParamBorder.TranslatePoint(borderMidPoint, relativeTo);
        }

        #endregion
    }
}
